/*********************************************************************************
 *      Copyright:  (C) 2025 YANG Studio
 *                  All rights reserved.
 *
 *       Filename:  dht11.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(27/05/25)
 *         Author:  YANG JIAYU <yangjiayu@gmail.com>
 *      ChangeLog:  1, Release initial version on "27/05/25 18:54:46"
 *                 
 ********************************************************************************/
#include "./BSP/DHT11/dht11.h"  // 包含DHT11传感器驱动头文件
#include "./SYSTEM/delay/delay.h"  // 包含延时函数头文件

/**
 * @brief       复位DHT11传感器（发送开始信号）
 * @param       无
 * @retval      无
 */
static void dht11_reset(void)
{
    DHT11_DQ_OUT(0);    /* 拉低数据线，主机发送起始信号 */
    delay_ms(20);       /* 保持低电平至少18ms（DHT11要求） */
    DHT11_DQ_OUT(1);    /* 拉高数据线 */
    delay_us(30);       /* 主机拉高10~35us，等待DHT11响应 */
}

/**
 * @brief       等待DHT11的响应信号
 * @param       无
 * @retval      0: DHT11响应正常  1: DHT11无响应或不存在
 */
uint8_t dht11_check(void)
{
    uint8_t retry = 0;  /* 重试计数器 */
    uint8_t rval = 0;   /* 返回值 */

    /* 等待DHT11拉低数据线（约83us） */
    while (DHT11_DQ_IN && retry < 100)  
    {
        retry++;
        delay_us(1);    /* 每次等待1us */
    }

    if (retry >= 100)   /* 超时未等到低电平 */
    {
        rval = 1;       /* 返回错误 */
    }
    else
    {
        retry = 0;

        /* 等待DHT11再次拉高数据线（约87us） */
        while (!DHT11_DQ_IN && retry < 100) 
        {
            retry++;
            delay_us(1);
        }
        if (retry >= 100) rval = 1;  /* 超时未等到高电平 */
    }
    
    return rval;        /* 返回检查结果 */
}

/**
 * @brief       从DHT11读取一个位数据
 * @param       无
 * @retval      读取到的位值: 0 或 1
 */
uint8_t dht11_read_bit(void)
{
    uint8_t retry = 0;

    /* 等待数据线变为低电平（位开始信号） */
    while (DHT11_DQ_IN && retry < 100)  
    {
        retry++;
        delay_us(1);
    }

    retry = 0;

    /* 等待数据线变为高电平（数据位开始） */
    while (!DHT11_DQ_IN && retry < 100) 
    {
        retry++;
        delay_us(1);
    }

    delay_us(40);       /* 等待40us：判断数据位是0还是1的关键时间点 */

    if (DHT11_DQ_IN)    /* 根据数据线状态返回位值 */
    {
        return 1;       /* 高电平表示1 */
    }
    else 
    {
        return 0;       /* 低电平表示0 */
    }
}

/**
 * @brief       从DHT11读取一个字节数据（8位）
 * @param       无
 * @retval      读取到的字节数据
 */
static uint8_t dht11_read_byte(void)
{
    uint8_t i, data = 0;

    for (i = 0; i < 8; i++)         /* 循环读取8位数据 */
    {
        data <<= 1;                 /* 左移一位，为下一位数据腾出位置 */
        data |= dht11_read_bit();   /* 读取1位数据并添加到字节中 */
    }

    return data;                    /* 返回完整字节 */
}

/**
 * @brief       从DHT11读取一次完整数据（温度+湿度）
 * @param       temp: 温度值指针（范围:-20~60℃）
 * @param       humi: 湿度值指针（范围:5%~95%）
 * @retval      0: 读取成功  1: 读取失败
 */
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
    uint8_t buf[5];  /* 存储DHT11返回的5字节数据 */
    uint8_t i;

    dht11_reset();   /* 发送复位信号 */

    if (dht11_check() == 0)  /* 检查DHT11响应正常 */
    {
        for (i = 0; i < 5; i++)  /* 读取40位数据（5字节） */
        {
            buf[i] = dht11_read_byte();  /* 读取每个字节 */
        }

        /* 校验和验证：前四个字节相加等于第五个字节 */
        if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
        {
            *humi = buf[0];    /* 湿度整数部分 */
            *temp = buf[2];    /* 温度整数部分 */
        }
    }
    else
    {
        return 1;  /* 响应失败，返回错误 */
    }
    
    return 0;      /* 读取成功 */
}

/**
 * @brief       初始化DHT11的IO口并检查传感器是否存在
 * @param       无
 * @retval      0: 初始化成功  1: 传感器不存在或异常
 */
uint8_t dht11_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    DHT11_DQ_GPIO_CLK_ENABLE();     /* 开启数据线GPIO时钟 */

    /* 配置GPIO为开漏输出模式 */
    gpio_init_struct.Pin = DHT11_DQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;    /* 开漏输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;            /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;  /* 高速 */
    HAL_GPIO_Init(DHT11_DQ_GPIO_PORT, &gpio_init_struct);  /* 初始化GPIO */

    /* 开漏输出模式下，输出高电平时可读取外部信号，无需切换IO方向 */

    dht11_reset();       /* 复位DHT11 */
    return dht11_check();  /* 返回检查结果 */
}

